'''
Created on Feb 3, 2013

@author: kafkef
'''



from PyQt4.QtGui import *
from PyQt4 import QtCore 
from PyQt4.QtCore import SIGNAL, Qt, QSettings, QPoint
from PyQt4.QtCore import QAbstractTableModel, QModelIndex, QVariant

from dexen.system import client
from dexen.system.client import ui
from dexen.system.common.states import JobState
from dexen.system.client.db import ServerInfo, JobInfo
from dexen.system.common.excepts import DexenConnectionError
from dexen.system.client.ui.widgets.table_view import TableView


JOB_NAME = 0
JOB_STAT = 1
START_TIME = 2
ELAPSED_TIME = 3
TOTAL_BIRTH = 4
AVG_TIME_IND = 5

_labels = [
"Job Name:",
"Job Status:",
"Start Time:",
"Elapsed Time:",
"Total Birth:",
"Avg time per individual:"
]

svr_proxy = client.GetServerProxy()

_joblist_wdg = None
_jobsummary_wdg = None
_jobsinfo_wdg = None


def GetJobListWdg():
    global _joblist_wdg
    if _joblist_wdg is None:
        _joblist_wdg = JobListWdg()
    return _joblist_wdg

def GetJobSummaryWdg():
    global _jobsummary_wdg
    if _jobsummary_wdg is None:
        _jobsummary_wdg = JobSummaryWdg()
    return _jobsummary_wdg

def GetJobsInfoWdg():
    global _jobsinfo_wdg
    if _jobsinfo_wdg is None:
        _jobsinfo_wdg = JobsInfoWdg()
    return _jobsinfo_wdg

#===============================================================================
# JobListWdg
#===============================================================================
class JobListWdgItem(QTreeWidgetItem):
    def __init__(self, parent, _list=None, obj=None):
        if obj is not None:
            _list = [obj.get_name(), obj.get_start_time_str(), obj.get_status()]
        assert _list is not None
        super(JobListWdgItem, self).__init__(parent, _list)
        self.obj = obj
        
    def set_obj(self, obj):
        if obj is None: return
        self.obj = obj
        self.setText(2, obj.get_status())
    
    def get_obj(self):
        return self.obj

class JobListWdg(QWidget):
    def __init__(self):
        super(JobListWdg, self).__init__()
        self._create_widgets()
        self.svr_info = ServerInfo()
        self.jobs = {} # JobInfo : JobListWdgItem
        self.svrWdgItem = None # JobListWdgItem
        self.addToLocalAct = QAction("View Job Data...", self)
        self.addToLocalAct.triggered.connect(self.add_to_local)
        self.timer = QtCore.QTimer()
        self.connect(self.timer, SIGNAL("timeout()"), self.refresh)
    
    def add_to_local(self):
        localsiteWdg = ui.GetLocalJobListWdg()
        job_info = self.current_job()
        localsiteWdg.add_job(job_info)
    
    def _create_widgets(self):
        layout = QVBoxLayout()
        
        self.titleLabel = QLabel("Remote Site", self)
        self.treeWdg = QTreeWidget(self)
        self.treeWdg.setColumnCount(3)
        self.treeWdg.setHeaderLabels(["Job Name", "Start Time", "Status"])
        self.treeWdg.setItemsExpandable(True)
        self.treeWdg.setContextMenuPolicy(Qt.CustomContextMenu)

        layout.addWidget(self.titleLabel)
        layout.addWidget(self.treeWdg)
        self.setLayout(layout)
        
        self.restore_settings()
        
        self.connect(self.treeWdg, SIGNAL("customContextMenuRequested(const QPoint &)"),
                     self._show_popup_menu)
        self.connect(self.treeWdg, SIGNAL("currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)"),
                     self._on_current_item_changed)        
    
    def _show_popup_menu(self, point):
        from dexen.system.client.ui import main_window as mw
        mainWindow = mw.GetMainWindow()
        assert isinstance(point, QPoint)
        assert isinstance(mainWindow, mw.MainWindow)
        
        item = self.treeWdg.itemAt(point)
        if item is None:
            return
        
        assert isinstance(item, QTreeWidgetItem)
        menu = QMenu(self.treeWdg)

        if item.parent() is None:
            menu.addAction(mainWindow.addJobAct)
        else:
            menu.addAction(self.addToLocalAct)
            menu.addAction(mainWindow.stopJobAct)
            menu.addAction(mainWindow.deleteRemoteJobAct)
            job_info = item.get_obj()
            assert isinstance(job_info, JobInfo)
            if job_info.is_running():
                mainWindow.deleteRemoteJobAct.setEnabled(False)
                mainWindow.stopJobAct.setEnabled(True)
            else:
                mainWindow.deleteRemoteJobAct.setEnabled(True)
                mainWindow.stopJobAct.setEnabled(False)

        menu.exec_(self.treeWdg.mapToGlobal(point))
    
    def _on_current_item_changed(self, cur_item, prev_item):
        if cur_item is not None:
            GetJobsInfoWdg().on_cur_job_changed(cur_item.get_obj())
    
    def on_disconnect(self):
        self.timer.stop()
        assert not svr_proxy.is_connected()
        self.treeWdg.clear()
        self.jobs.clear()
        ui.GetLocalJobListWdg().on_disconnect()
    
    def on_connect(self):
        assert svr_proxy.is_connected()
        self.svr_info = svr_proxy.get_svr_info()
        svr_name = "%s (%s)" %(svr_proxy.get_name(), svr_proxy.get_ip())
        self.svrWdgItem = JobListWdgItem(self.treeWdg, 
                          [svr_name, self.svr_info.get_start_time_str(), "Connected"])
        self.treeWdg.setCurrentItem(self.svrWdgItem)
        self.refresh()
        ui.GetLocalJobListWdg().on_connect(self.svr_info)
        self.timer.start(10000)
        
    def refresh(self):
        #qDebug("JobListWdg::refresh()")
        try:
            jobs = svr_proxy.get_job_infos()
        except DexenConnectionError:
            ui.GetMainWindow().on_disconnect(self.svr_info)
            return
        # Remove the jobs that do not exist anymore, update it otherwise.
        for job_info, item in self.jobs.items():
            if job_info not in jobs:
                self.svrWdgItem.removeChild(item)
                del self.jobs[job_info]
            else:
                item.set_obj(jobs[jobs.index(job_info)])
        # Add newly started jobs
        for job_info in jobs:
            if not self.jobs.has_key(job_info):
                self.jobs[job_info] = JobListWdgItem(self.svrWdgItem, obj=job_info)
        self.treeWdg.expandAll()
        GetJobSummaryWdg().on_jobs_info_updated(jobs)
        ui.GetLocalJobListWdg().update_jobs(jobs)

    def current_job(self):  
        return self.treeWdg.currentItem().get_obj()

    def save_settings(self):
        settings = QSettings()
        settings.beginGroup("RemoteJobListWdg")
        settings.setValue("Header", self.treeWdg.header().saveState())
        settings.endGroup()
    
    def restore_settings(self):
        settings = QSettings()
        settings.beginGroup("RemoteJobListWdg")
        self.treeWdg.header().restoreState(settings.value("Header", "").toByteArray())
        settings.endGroup()

#===============================================================================
# 
#===============================================================================
class JobInfoModel(QAbstractTableModel):
    def __init__(self, info_items, parent=None):
        super(JobInfoModel, self).__init__(parent)
        self._info_items = info_items
        self._header = ["Task Name", "Input Size", "Pending", "Processing",
                        "Success", "Failure", "Avg Execution Time"]
    
    def rowCount(self, index=QModelIndex()):
        return len(self._info_items)
    
    def columnCount(self, index=QModelIndex()):
        return len(self._header)

    def data(self, index, role):
        assert isinstance(index, QModelIndex)
        if not index.isValid(): return QVariant()
        
        if role == Qt.BackgroundRole:
            return QColor(Qt.lightGray)
        
        if role == Qt.DisplayRole:
            item = self._info_items[index.row()][index.column()]
            if index.column() == 6:
                return QVariant("{0:.3f}".format(item) + " seconds")
            else:
                return QVariant(item)

    def headerData(self, section, orientation, role=Qt.DisplayRole):
        if role != Qt.DisplayRole:
            return QVariant()
        if orientation == Qt.Horizontal:
            return QVariant(self._header[section])


class JobSummaryWdg(QWidget):
    def __init__(self):
        super(JobSummaryWdg, self).__init__()
        self.widgets = {}
        self.cur_job = None # JobInfo
        self.jobs = {} # JobInfo: JobInfo
        self._create_widgets()
        
    def _create_widgets(self):
        # Create widgets
        for i in xrange(6):
            self.widgets[i] = QLabel()
        self.tableView = TableView()
        self.breakdownLabel = QLabel("Task executions breakdown:")

        # Configure widgets
        self.tableView.setModel(JobInfoModel([]))
        header = self.tableView.horizontalHeader()
        assert isinstance(header, QHeaderView)
        self.tableView.resizeColumnsToContents()
        
        # Setup layout
        vLayout = QVBoxLayout()
        formLayout = QFormLayout()
        formLayout.setFormAlignment(Qt.AlignLeft | Qt.AlignTop)
        formLayout.setLabelAlignment(Qt.AlignLeft)
        for i in xrange(6):
            formLayout.addRow(_labels[i], self.widgets[i])
        
        groupBox = QGroupBox("Job Summary")
        groupBox.setLayout(formLayout)
        
        vLayout.addWidget(groupBox)
        vLayout.addWidget(QLabel(""))
        vLayout.addWidget(self.breakdownLabel)
        vLayout.addWidget(self.tableView)
        self.setLayout(vLayout)

    def _update_widget(self, index, value):
        label = self.widgets[index]
        assert isinstance(label, QLabel)
        if index == AVG_TIME_IND:
            label.setText("{0:.3f} seconds".format(value))
        else:
            label.setText(str(value))
    
    def _update_widgets(self):
        assert self.cur_job is not None
        # update widgets
        self._update_widget(JOB_NAME, self.cur_job.get_name())
        self._update_widget(JOB_STAT, self.cur_job.get_status())
        self._update_widget(START_TIME, self.cur_job.get_start_time_str())
        
        job_state = self.cur_job.get_job_state()
        assert isinstance(job_state, JobState)
        self._update_widget(ELAPSED_TIME, job_state.get_elapsed_time())
        self._update_widget(TOTAL_BIRTH, job_state.tot_births)
        self._update_widget(AVG_TIME_IND, job_state.get_avg_time_per_ind())
        
        tasks_info = job_state.get_tasks_info()
        model = JobInfoModel(tasks_info)
        self.tableView.setModel(model)
        self.tableView.resizeColumnsToContents()

    def _clear_widgets(self):
        self.tableView.setModel(JobInfoModel([]))
        for widget in self.widgets.values():
            widget.setText("")

    def on_connect(self):
        pass
    
    def on_disconnect(self):
        self._clear_widgets()

    def on_jobs_info_updated(self, job_info_list):
        for job_info in job_info_list:
            self.jobs[job_info] = job_info
        # important to update the cur_job
        if self.cur_job:
            self.cur_job = self.jobs[self.cur_job]
            self._update_widgets()

    def on_delete_job(self, job_info):
        if self.jobs.has_key(job_info):
            del self.jobs[job_info]

    def on_cur_job_changed(self, job_info):
        self.cur_job = job_info
        if self.cur_job is None:
            self._clear_widgets()
        else:
            self.jobs[job_info] = job_info
            self._update_widgets()


class JobsInfoWdg(QWidget):
    def __init__(self):
        super(JobsInfoWdg, self).__init__()
        self._create_widgets()

    def _create_widgets(self):
        self.splitter = QSplitter(Qt.Vertical)
        self.jobListWdg = GetJobListWdg()
        self.jobSummaryWdg = GetJobSummaryWdg()
        
        layout = QVBoxLayout()
        self.splitter.addWidget(self.jobListWdg)
        self.splitter.addWidget(self.jobSummaryWdg)
        layout.addWidget(self.splitter)
        self.setLayout(layout) 
    
    def on_cur_job_changed(self, job_info):
        self.jobSummaryWdg.on_cur_job_changed(job_info)
    
    def refresh(self):
        self.jobListWdg.refresh()
    
    def on_connect(self):
        self.jobListWdg.on_connect()
        self.jobSummaryWdg.on_connect()
    
    def on_disconnect(self):
        self.jobListWdg.on_disconnect()
        self.jobSummaryWdg.on_disconnect()

    def save_settings(self):
        self.jobListWdg.save_settings()
        settings = QSettings()
        settings.beginGroup("RemoteJobsInfo")
        settings.setValue("Splitter", self.splitter.saveState())
        settings.endGroup()
            
    def restore_settings(self):
        self.jobListWdg.restore_settings()
        settings = QSettings()
        settings.beginGroup("RemoteJobsInfo")
        self.splitter.restoreState(settings.value("Splitter").toByteArray())
        settings.endGroup()        






